package redis

import (
	"fmt"
	"github.com/gomodule/redigo/redis"
	"sieve_engine/lib/app"
	"sieve_engine/lib/config"
	"sieve_engine/lib/util/convert"
	"strings"
	"sync"
	"time"
)

var (
	ClientPoolMaxActive = 10
	Separator           = "/"
)

type Server struct {
	Addr      string
	Pwd       string
	DB        int
	Prefix    string
	Separator string
	MaxConn   int
}

func (s *Server) separator() string {
	if s.Separator == "" {
		return Separator
	}
	return s.Separator
}

func (s *Server) equalsTo(o *Server) bool {
	return s.Addr == o.Addr &&
		s.Pwd == o.Pwd &&
		s.DB == o.DB &&
		s.Prefix == o.Prefix &&
		s.Separator == o.Separator &&
		s.MaxConn == o.MaxConn
}

func NewServer(addr string) *Server               { return &Server{Addr: addr} }
func (s *Server) SetAddr(addr string) *Server     { s.Addr = addr; return s }
func (s *Server) SetPwd(pwd string) *Server       { s.Pwd = pwd; return s }
func (s *Server) SetDB(db int) *Server            { s.DB = db; return s }
func (s *Server) SetPrefix(prefix string) *Server { s.Prefix = prefix; return s }
func (s *Server) SetSeparator(sep string) *Server { s.Separator = sep; return s }
func (s *Server) SetMaxConn(maxconn int) *Server  { s.MaxConn = maxconn; return s }

// Deprecated
func FromConfig(key string, addr, prefix string) *Server {
	return &Server{
		Addr:   *config.String(key+".addr", addr, ""),
		Pwd:    *config.String(key+".pwd", "", ""),
		Prefix: *config.String(key+".prefix", prefix, ""),
	}
}

func serverFromConfig(key string) func() *Server {
	var (
		addr    = config.String(key+".addr", "", "")
		pwd     = config.String(key+".pwd", "", "")
		db      = config.Int(key+".db", 0, "")
		prefix  = config.String(key+".prefix", "", "")
		maxconn = config.Int(key+".maxconn", 0, "")
	)
	return func() *Server {
		return &Server{
			Addr:    *addr,
			Pwd:     *pwd,
			DB:      *db,
			Prefix:  *prefix,
			MaxConn: *maxconn,
		}
	}
}

func NewClientFromConfig(key string) *Client {
	server := serverFromConfig(key)
	c := Connect(server())
	config.OnReload(func() {
		c.SetServer(server())
	})
	return c
}

type Client struct {
	pool *redis.Pool

	server    *Server
	prefix    string
	separator string

	lock sync.Mutex
}

func Connect(server *Server) *Client {
	c := &Client{}
	c.SetServer(server)
	return c
}

func (c *Client) SetServer(server *Server) {
	if c.server != nil && c.server.equalsTo(server) {
		return
	}
	c.lock.Lock()
	defer c.lock.Unlock()
	c.server = server
	c.prefix = strings.Trim(server.Prefix, server.separator())
	c.separator = server.separator()

	checkAndCloseOldPool(c.pool)

	maxConn := ClientPoolMaxActive
	if server.MaxConn > 0 {
		maxConn = server.MaxConn
	}
	c.pool = &redis.Pool{
		Dial: func() (redis.Conn, error) {
			conn, err := redis.Dial("tcp", server.Addr)
			if err != nil {
				return nil, err
			}
			if server.Pwd != "" {
				if _, err := conn.Do("AUTH", server.Pwd); err != nil {
					conn.Close()
					return nil, err
				}
			}
			if _, err := conn.Do("SELECT", server.DB); err != nil {
				conn.Close()
				return nil, err
			}
			return conn, nil
		},
		MaxIdle:   maxConn,
		MaxActive: maxConn,
		Wait:      true,
	}
}

func checkAndCloseOldPool(pool *redis.Pool) {
	if pool == nil {
		return
	}
	app.Async(func() {
		defer pool.Close()
		for i := 0; i < 10; i++ {
			if s := pool.Stats(); s.ActiveCount == s.IdleCount {
				return
			}
			time.Sleep(time.Second)
		}
	})
}

func (c *Client) GetServer() *Server {
	s := c.server
	if s == nil {
		return nil
	}
	return &Server{
		Addr:      s.Addr,
		Pwd:       s.Pwd,
		DB:        s.DB,
		Prefix:    s.Prefix,
		Separator: s.Separator,
		MaxConn:   s.MaxConn,
	}
}

func (c *Client) Close() error {
	return c.pool.Close()
}

func (c *Client) k(key string) string {
	return c.prefix + c.separator + strings.Trim(key, c.separator)
}

func (c *Client) key(keys []string) *key {
	if c.prefix != "" {
		for i, k := range keys {
			keys[i] = c.k(k)
		}
	}
	cc := &key{c: c, keys: keys}
	if len(keys) > 0 {
		cc.key = keys[0]
	}
	return cc
}

func (c *Client) K(key ...any) string {
	var ss []string
	for _, k := range key {
		ss = append(ss, strings.Trim(convert.ToString(k), c.separator))
	}
	return strings.Join(ss, c.separator)
}

func (c *Client) FK(key ...any) string {
	return c.k(c.K(key...))
}

func (c *Client) Exec(command string, args ...any) Result {
	return &cmd{c: c, cmd: command, args: args}
}

//

func Print(o any) string {
	switch v := o.(type) {
	case *Client:
		return fmt.Sprint(*v.server)
	case *key:
		return fmt.Sprint(v.keys)
	case *cmd:
		s := v.cmd
		for _, vv := range v.args {
			s += " " + fmt.Sprint(vv)
		}
		return s
	}
	return ""
}
